package com.hmily.cloud.asm.core;

import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;

/**
 * <h1>生成class直接的工具类。</h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2024/6/1 10:43
 */
public class GenerateHandlerUtils implements Opcodes {

    private static Class staticNewClz = null;

    public static Class toClass(Class originClass, String newClzNameSuffix) throws Exception {
        if (staticNewClz != null) {
            return staticNewClz;
        }

        // 生成字节码字节数组
        byte[] classBytes = generateNewClassBytes(UserBean.class, "Handler");

        // 可以想办法将 classBytes 存储为一个文件
        FileOutputStream fileOutputStream = new FileOutputStream(new File(
                "/Users/hehui/Hmily/opensource/Asm/AsmSamples/src/main/java/com/hmily/cloud/asm/draft",
                originClass.getSimpleName() + newClzNameSuffix + ".class"
        ));
        fileOutputStream.write(classBytes);
        fileOutputStream.close();

        // 还得把 classBytes 加载到 JVM 内存中去，并返回 JVM 加载好的 Class 对象
        staticNewClz = loadClass(originClass.getName() + newClzNameSuffix, classBytes);
        return staticNewClz;
    }

    private static byte[] generateNewClassBytes(Class originClass, String newClzNameSuffix) {
        String newClassSimpleNameAndSuffix = originClass.getSimpleName() + newClzNameSuffix + ".java";
        ClassWriter classWriter = new ClassWriter(0);
        // class version 52.0 (52)
        //// access flags 0x21
        //// signature Ljava/lang/Object;Lcom/hmily/cloud/asm/core/Handler<Lcom/hmily/cloud/asm/core/UserBean;>;
        //// declaration: com/hmily/cloud/asm/core/UserBeanHandler implements com.hmily.cloud.asm.core.Handler<com.hmily.cloud.asm.core.UserBean>
        //public class com/hmily/cloud/asm/core/UserBeanHandler implements com/hmily/cloud/asm/core/Handler {
        classWriter.visit(
                V1_8,
                ACC_PUBLIC + ACC_SUPER,
                Type.getInternalName(originClass) + newClzNameSuffix,
                Type.getDescriptor(Object.class)+Type.getDescriptor(Handler.class).replace(";","")+"<"+Type.getDescriptor(originClass)+">;",
                Type.getInternalName(Object.class),
                new String[]{ Type.getInternalName(Handler.class)}
        );
        classWriter.visitSource(newClassSimpleNameAndSuffix, null);

        /**********************************************************************/
        // 创建构造方法
        /**********************************************************************/
        //
        //  // compiled from: UserBeanHandler.java
        //
        //  // access flags 0x1
        //  public <init>()V
        MethodVisitor initMethodVisitor = classWriter.visitMethod(
                ACC_PUBLIC,
                "<init>",
                "()V",
                null,
                null);
        {
            initMethodVisitor.visitCode();

            //   L0
            //    LINENUMBER 13 L0
            //    ALOAD 0
            initMethodVisitor.visitVarInsn(ALOAD, 0);
            //    INVOKESPECIAL java/lang/Object.<init> ()V
            initMethodVisitor.visitMethodInsn(INVOKESPECIAL,
                    Type.getInternalName(Object.class),
                    "<init>",
                    "()V"
            );
            //    RETURN
            initMethodVisitor.visitInsn(RETURN);

            //   L1
            Label L1 = new Label();
            initMethodVisitor.visitLabel(L1);
            //    LOCALVARIABLE this Lcom/hmily/cloud/asm/core/UserBeanHandler; L0 L1 0
            //    MAXSTACK = 1
            //    MAXLOCALS = 1
            initMethodVisitor.visitMaxs(1, 1);
            initMethodVisitor.visitEnd();
        }

        /**********************************************************************/
        // 创建 addBefore 方法
        /**********************************************************************/
        {
            //  // access flags 0x1
            //  public addBefore(Lcom/hmily/cloud/asm/core/UserBean;)V
            MethodVisitor methodVisitor = classWriter.visitMethod(
                    ACC_PUBLIC,
                    "addBefore",
                    "(" + Type.getDescriptor(originClass) + ")V",
                    null,
                    null
            );
            methodVisitor.visitCode();
            //   L0
            Label L0 = new Label();
            initMethodVisitor.visitLabel(L0);
            //    LINENUMBER 17 L0
            //    ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
            //    INVOKEVIRTUAL com/hmily/cloud/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));

            //    IFNULL L1
            Label L1 = new Label();
            methodVisitor.visitJumpInsn(IFNULL, L1);

            //    ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);

            //    INVOKEVIRTUAL com/hmily/cloud/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));

            //    INVOKEVIRTUAL java/lang/String.length ()I
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(String.class),
                    "length",
                    "()I");

            //    IFLE L1
            methodVisitor.visitJumpInsn(IFLE, L1);

            /**********************************************************************/
            // 接下来要干的事情就是：u.setName("#BEFORE#" + u.getName());
            /**********************************************************************/
            //   L2
            Label L2 = new Label();
            initMethodVisitor.visitLabel(L2);
            //    LINENUMBER 20 L2
            //    ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
            //    NEW java/lang/StringBuilder
            methodVisitor.visitTypeInsn(NEW, Type.getInternalName(StringBuilder.class));
            //    DUP
            methodVisitor.visitInsn(DUP);
            //    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
            methodVisitor.visitMethodInsn(INVOKESPECIAL,
                    Type.getInternalName(StringBuilder.class),
                    "<init>",
                    "()V");
            //    LDC "#BEFORE#"
            methodVisitor.visitLdcInsn("#BEFORE#");
            //    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "append",
                    "("+ Type.getDescriptor(String.class) + ")" + Type.getDescriptor(StringBuilder.class));
            //    ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
            //    INVOKEVIRTUAL com/hmily/cloud/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));
            //    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "append",
                    "("+ Type.getDescriptor(String.class) + ")" + Type.getDescriptor(StringBuilder.class));
            //    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "toString",
                    "()" + Type.getDescriptor(String.class));
            //    INVOKEVIRTUAL com/hmily/cloud/asm/core/UserBean.setName (Ljava/lang/String;)V
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "setName",
                    "(" + Type.getDescriptor(String.class)+")V");
            //   L1
            methodVisitor.visitLabel(L1);
            //    LINENUMBER 22 L1
            //   FRAME SAME
            methodVisitor.visitFrame(F_SAME, 0, null, 0, null);
            //    RETURN
            methodVisitor.visitInsn(RETURN);
            //   L3
            //    LOCALVARIABLE this Lcom/hmily/cloud/asm/core/UserBeanHandler; L0 L3 0
            //    LOCALVARIABLE u Lcom/hmily/cloud/asm/core/UserBean; L0 L3 1
            //    MAXSTACK = 3
            //    MAXLOCALS = 2
            methodVisitor.visitMaxs(3, 2);
            methodVisitor.visitEnd();
        }

        /**********************************************************************/
        // 创建桥接 addBefore 方法
        /**********************************************************************/
        {
            //  // access flags 0x1041
            //  public synthetic bridge addBefore(Ljava/lang/Object;)V
            MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC + ACC_BRIDGE,
                    "addBefore",
                    "(" + Type.getDescriptor(Object.class) + ")V",
                    null,
                    null
            );
            methodVisitor.visitCode();
            //   L0
            //    LINENUMBER 13 L0
            //    ALOAD 0
            methodVisitor.visitVarInsn(ALOAD, 0);
            //    ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
            //    CHECKCAST com/hmily/cloud/asm/core/UserBean
            methodVisitor.visitTypeInsn(CHECKCAST, Type.getInternalName(originClass));
            //    INVOKEVIRTUAL com/hmily/cloud/asm/core/UserBeanHandler.addBefore (Lcom/hmily/cloud/asm/core/UserBean;)V
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass) + newClzNameSuffix,
                    "addBefore",
                    "(" + Type.getDescriptor(originClass) + ")V");
            //    RETURN
            methodVisitor.visitInsn(RETURN);
            //   L1
            Label L1 = new Label();
            methodVisitor.visitLabel(L1);
            //    LOCALVARIABLE this Lcom/hmily/cloud/asm/core/UserBeanHandler; L0 L1 0
            //    MAXSTACK = 2
            //    MAXLOCALS = 2
            //}
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }

        classWriter.visitEnd();
        return classWriter.toByteArray();
    }

    /**
     * <h2>将 class 的字节码字节数组加载到 JVM 中。</h2>
     *
     * @param: classFullName：类的全类名，例: com.hmily.cloud.asm.core.UserBeanHandler
     * @param: classBytes
     * @return: java.lang.Class
     */
    public static Class loadClass(String classFullName, byte[] classBytes) throws Exception {
        // 还得把 classBytes 加载到 JVM 内存中去
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> loaderClass = Class.forName("java.lang.ClassLoader");
        Method defineClassMethod = loaderClass.getDeclaredMethod("defineClass",
                String.class,
                byte[].class,
                int.class,
                int.class);
        defineClassMethod.setAccessible(true);

        // 利用 ClassLoader 的 defineClass 方法想办法将 bytes 变成 Class 对象
        Object respObject = defineClassMethod.invoke(loader, new Object[]{
                classFullName,
                classBytes,
                0,
                classBytes.length
        });

        return (Class) respObject;
    }
}